#include <crsparse.h>
using namespace crsparse;
#include <crhuffmanex.h>
using namespace crhuffman;

struct header_mask
{
	static const byte use_sparse = 0x40;
	static const byte tiny_sizes = 0x20;
	static const byte half_bytes = 0x10;
	static const byte no_huffman = 0x08;
	static const byte bi_of_size = 0x07;
};

#define TINYMODE
#ifdef TINYMODE
#endif

inline void prepare(huffman_builder& builder0, huffman_builder& builder1, istream_base& is)
{
	sparse_encoder<huffman_builder> sparser;
	sparser.setnext(&builder1);

	try {
		while (true) {
			byte ch = is.getbyte();
			builder0.push(ch);
			sparser.push(ch);
		}
	}
	catch (exception<stream_base>& e)
	{
		if (e.code == istream_base::eos && e.obj == &is)
			sparser.final();
		else CR_Prompt(_T("builder : istream_base raised unknown exception"));
	}
}

inline void compress(huffman_builder& builder, istream_base& is, ostream_base& os)
{
	huffman_encoder encoder;
	char obi;

#ifdef TINYMODE
	//const bool use_sparse = plain < len;
	const bool use_sparse = true;
	////////////////////////////////////////////////////////////////////////////

	huffman_builder& huffmanbuilder = 
		use_sparse ? huffmanbuilder1 : huffmanbuilder0;

	if (!huffmanbuilder.analyse(plain))
	{
		throw exception<void>(header_mask::no_huffman);
	}

	try {
		if (plain <= mini_doc) {
			frequencies_wrap<16> freqs;
			freq_merge(freqs, huffmanbuilder);
			code_table_wrap<16> codes;
			huffman_tree<16> tree;
			tree.build(freqs.freqs_);
			tree.generate(codes.codes_);
			huffman_header<16> header;
			bit_size bsize;
			bool tiny_sizes = header.calcsize(
				codes.codes_, freqs.freqs_, bsize);
			uint32 enclen = bsize.oBy;
			byte flag = header_mask::bi_of_size & bsize.obi
				| header_mask::half_bytes;
			if (tiny_sizes)
				flag |= header_mask::tiny_sizes;
			if (use_sparse)
				flag |= header_mask::use_sparse;
			os.putuint(is.remain());
			os.putuint(enclen);
			os.putbyte(flag);
			bit_addr pos = header.writecodes(os, codes.codes_, tiny_sizes);
			code_merge(encoder, codes);
			encoder.bind(pos, os.remain()*8);
		}
		else {
			huffman_tree<256> tree;
			tree.build(huffmanbuilder.freqs_);
			tree.generate(encoder.codes_);
			huffman_header<256> header;
			bit_size bsize;
			bool tiny_sizes = header.calcsize(
				encoder.codes_, huffmanbuilder.freqs_, bsize);
			uint32 enclen = bsize.oBy;
			byte flag = header_mask::bi_of_size & bsize.obi;
			if (tiny_sizes)
				flag |= header_mask::tiny_sizes;
			if (use_sparse)
				flag |= header_mask::use_sparse;
			os.putuint(is.remain());
			os.putuint(enclen);
			os.putbyte(flag);
			bit_addr pos = header.writecodes(os, encoder.codes_, tiny_sizes);
			encoder.bind(pos, os.remain()*8);
		}
	}
	catch (exception<huffman_tree<256>>& e)
	{
		switch (e.code)
		{
		case huffman_tree<256>::load_damaged_stream:
			CR_Prompt(_T("huffman_tree : load_damaged_stream"));break;
		case huffman_tree<256>::grow_damaged_stream:
			CR_Prompt(_T("huffman_tree : grow_damaged_stream"));break;
		case huffman_tree<256>::step_damaged_stream:
			CR_Prompt(_T("huffman_tree : step_damaged_stream"));break;
		default: CR_Prompt(_T("huffman_tree : raised unknown exception"));
		}
	}

	return use_sparse;
}

inline bit_addr shecompress(istream_base& is, ostream_base& os, huffman_encoder& encoder, byte use_sparse)
{
	TRACE(_T("****** encoding bit stream: ******\n"));
	sparse_encoder<huffman_encoder> sparseencoder;

	try {
		if (use_sparse) {
			sparseencoder.setnext(&encoder);
			sparseencoder.input(is.tell(), is.remain());
		}
		else {
			while (true) {
				byte ch = is.getbyte();
				encoder.push(ch);
			}
		}
	}
	catch (exception<istream_base>& e)
	{
		if (e.code == istream_base::eos)
		{}
		else CR_Prompt(_T("encoder : istream_base raised unknown exception"));
	}
	catch (exception<ostream_base>& e)
	{
		if (e.code == ostream_base::eos)
			CR_Prompt(_T("encoder : ostream_base has no enough output space"));
		else CR_Prompt(_T("encoder : ostream_base raised unknown exception"));
	}

	sparseencoder.final();

	DEBUGING(encoder.dump(std::ofstream("g:\\text_enc.txt")));
	TRACE(_T("\n"));

	return encoder.tell();
}

inline byte* decompress(istream_base& is, ostream_base& os)
{
	const uint32 declen = is.getuint();
	const uint32 enclen = is.getuint();
	const byte flag = is.getbyte();
	const byte use_sparse = flag & header_mask::use_sparse;
	const bit_addr endpos(is.tell() + enclen, flag & header_mask::bi_of_size);

	if (flag & header_mask::no_huffman) {
		TRACE(_T("not compressed with huffman.\n"));
		if (use_sparse) {
			direct_pusher directdecoder(os);

			sparse_decoder<direct_pusher> sparsedecoder;
			sparsedecoder.setnext(&directdecoder);
			sparsedecoder.input(is.tell(), is.remain());
			ASSERT(os.occupy() == declen);
		}
		else {
			iostransfer(os, is, is.remain());
		}
		return os.tell();
	}
	TRACE(_T("****** load codes: ******\n"));
	bit_addr pos;
	code_table_wrap<256> codes;
	if (flag & header_mask::half_bytes) {
		code_table_wrap<16> codes16;
		huffman_header<16> header;
		pos = header.readcodes(is, codes16.codes_, 
			!!(flag & header_mask::tiny_sizes));
		code_merge(codes, codes16);
	}
	else {
		huffman_header<256> header;
		pos = header.readcodes(is, codes.codes_, 
			!!(flag & header_mask::tiny_sizes));
	}

	try {
		direct_pusher directdecoder(os);
		if (use_sparse) {
			huffman_decoder<sparse_decoder<direct_pusher>> huffmandecoder(is);
			sparse_decoder<direct_pusher> sparsedecoder;
			huffmandecoder.setnext(&sparsedecoder);
			sparsedecoder.setnext(&directdecoder);
			TRACE(_T("****** restore tree: ******\n"));
			huffmandecoder.restore(codes.codes_);
			TRACE(_T("\n"));
			huffmandecoder.bind(pos, is.remain()*8);
			TRACE(_T("****** decoding bit stream: ******\n"));
			huffmandecoder.bitscan(endpos);
			sparsedecoder.final();
			DEBUGING(huffmandecoder.dump(std::ofstream("g:\\text_dec.txt")));
			TRACE(_T("\n"));
		}
		else {
			huffman_decoder<direct_pusher> huffmandecoder(is);
			huffmandecoder.setnext(&directdecoder);
			TRACE(_T("****** restore tree: ******\n"));
			huffmandecoder.restore(codes.codes_);
			TRACE(_T("\n"));
			huffmandecoder.bind(pos, is.remain()*8);
			TRACE(_T("****** decoding bit stream: ******\n"));
			huffmandecoder.bitscan(endpos);
			DEBUGING(huffmandecoder.dump(std::ofstream("g:\\text_dec.txt")));
			TRACE(_T("\n"));
		}
	}
	catch (exception<huffman_tree<256>>& e)
	{
		switch (e.code)
		{
		case huffman_tree<256>::load_damaged_stream:
			CR_Prompt(_T("huffman_tree : load_damaged_stream"));break;
		case huffman_tree<256>::grow_damaged_stream:
			CR_Prompt(_T("huffman_tree : grow_damaged_stream"));break;
		case huffman_tree<256>::step_damaged_stream:
			CR_Prompt(_T("huffman_tree : step_damaged_stream"));break;
		default: CR_Prompt(_T("huffman_tree : raised unknown exception"));
		}
	}
	catch (exception<sparse_decoder<direct_pusher>>& e)
	{
		switch (e.code)
		{
		case sparse_decoder<direct_pusher>::parse_wrong_rep:
		default: CR_Prompt(_T("sparse error : parse_wrong_rep"));
		}
	}

	byte* end = os.tell();
	return end;
}

int _tmain(int argc, _TCHAR* argv[])
{
	static const char testdoc[] = 
#if 0
#endif 
		"0123456789/*aa00000000000000000000000011111111111111"
		"0123456789/*aa00000000000000000000000011111111111111"
		"0123456789/*aa00000000000000000000000011111111111111"
		"0123456789/*aa00000000000000000000000011111111111111"
		"0123456789/*aa00000000000000000000000011111111111111"
		"0123456789/*aa00000000000000000000000011111111111111"
		"0123456789/*aa00000000000000000000000011111111111111"
		"0123456789/*aa00000000000000000000000011111111111111"
		"0123456789/*aa00000000000000000000000011111111111111"
		"0123456789/*aa00000000000000000000000011111111111111"
		"0123456789/*aa00000000000000000000000011111111111111"
		"0123456789/*aa00000000000000000000000011111111111111"
		"0123456789/*aa00000000000000000000000011111111111111"
		"0123456789/*aa00000000000000000000000011111111111111"
		"0123456789/*aa00000000000000000000000011111111111111"
		"0123456789/*aa00000000000000000000000011111111111111"
		"0123456789/*aa00000000000000000000000011111111111111"
		"0123456789/*aa00000000000000000000000011111111111111"
		"0123456789/*aa00000000000000000000000011111111111111"
		"0123456789/*aa00000000000000000000000011111111111111"
		"0123456789/*aa00000000000000000000000011111111111111"
		"0123456789/*aa00000000000000000000000011111111111111"
		"0123456789/*aa00000000000000000000000011111111111111"
		"0123456789/*aa00000000000000000000000011111111111111"
		"0123456789/*aa00000000000000000000000011111111111111"
		"0123456789/*aa00000000000000000000000011111111111111"
		"0123456789/*aa00000000000000000000000011111111111111"
		"0123456789/*aa00000000000000000000000011111111111111"
		"0123456789/*aa00000000000000000000000011111111111111"
		"0123456789/*aa00000000000000000000000011111111111111"
		"0123456789/*aa00000000000000000000000011111111111111"
		"0123456789/*aa00000000000000000000000011111111111111"
		"0123456789/*aa00000000000000000000000011111111111111"
		"0123456789/*aa00000000000000000000000011111111111111"
		"0123456789/*aa00000000000000000000000011111111111111"
		"0123456789/*aa00000000000000000000000011111111111111"
		"0123456789/*aa00000000000000000000000011111111111111"
		"0123456789/*aa00000000000000000000000011111111111111"
		"0123456789/*aa00000000000000000000000011111111111111"
		"0123456789/*aa00000000000000000000000011111111111111"
		"0123456789/*aa00000000000000000000000011111111111111"
		"0123456789/*aa00000000000000000000000011111111111111"
		"0123456789/*aa00000000000000000000000011111111111111"
		"0123456789/*aa00000000000000000000000011111111111111"
		"0123456789/*aa00000000000000000000000011111111111111"
		"0123456789/*aa00000000000000000000000011111111111111"
		"0123456789/*aa00000000000000000000000011111111111111"
		"0123456789/*aa00000000000000000000000011111111111111"
		"0123456789/*aa00000000000000000000000011111111111111"
		"0123456789/*aa00000000000000000000000011111111111111"
		"0123456789/*aa00000000000000000000000011111111111111";

	static const size_t doclen = strlen(testdoc)+1;

	byte encbuf[4096];
	byte decbuf[4096];

	printf("****** origin document: ******\n");
	printf(testdoc);
	printf("\n");

	istream_base is;
	ostream_base os;

	is.bind((byte*)testdoc, doclen);
	os.bind(encbuf, 4096);

	huffman_encoder encoder(os);
	byte use_sparse;

	try {
		use_sparse = precompress(is, os, encoder);
		is.bind((byte*)testdoc, doclen);
		shecompress(is, os, encoder, use_sparse);
	}
	catch (exception<void>&) {
		rawcompress(is, os, use_sparse);
	}

	is.bind(encbuf, os.occupy());
	os.bind(decbuf, 4096);

	decompress(is, os);

	printf("****** target document: ******\n");
	printf((char*)decbuf);
	printf("\n");

	printf("****** follow document: ******\n");
	direct_pusher(os).input((byte*)"=end=", 5);
	os.tell()[20] = 0;
	printf((char*)os.tell()-5);
	printf("\n");

	system("pause");
	return 0;
}

